<?php
require_once 'IString.php';

class String extends Object implements IString, Serializable {
	public function __construct($string = '') {
		$this->atom = (string) $string;
	}

	public static function create ($string = '') {
		return new self ($string);	
	}

	public function __toString () {
		return $this->atom;
	}

	public function get () {
		return $this->atom;
	}

	public function set ($string) {
		$this->atom = (string) $string;
		return $this;
	}

	public function equals ($string) {
		return ($this->atom == strval($string));
	}


	// Core
	public function length () {
		return $this->toObject(Str::length ($this->atom));
	}

	public function size () {
		return $this->toObject(Str::size ($this->atom));
	}

	public function add ($string) {
		$this->atom .= $string;
		return $this;
	}

	public function insert ($string, $pos = 0) {
		$pos = $this->toAtom($pos);
		$substr = $this->isMb() ? 'mb_substr' : 'substr';
		$this->atom = (is_int($pos) and $pos) ?
			$substr($this->atom, 0, $pos) . $string . $substr($this->atom, $pos):
			$string . $this->atom;
		return $this;
	}

	const ENCODE = 1;
	const DECODE = 2;

	public function url ($mode) {
		return $this->func($mode == self::ENCODE ? 'urlencode' : 'urldecode');
	}

	// "Str" library by Koc
	public function take ($what, $type = Str::RIGHT, $caseSensitive = Str::CS) {
		$type = $this->toAtom($type);
		return $this->set(Str::take($what, $this->atom, $type, $caseSensitive));
	}

	public function find ($what, $type = Str::BOOL, $caseSensitive = Str::CS, $from = 0) {
		$type = $this->toAtom($type);
		$from = $this->toAtom($from);
		return $this->toObject(Str::find($what, $this->atom, $type, $caseSensitive, $from));
	}

	public function changeCase ($what = Str::ALL, $mode = Str::UP) {
		$what = $this->toAtom($what);
		$mode = $this->toAtom($mode);
		return $this->set(Str::changeCase($this->atom, $what, $mode));
	}

	public function base64 ($action = Str::ENCODE, $urlSafe = false) {
		$action = $this->toAtom($action);
		return $this->set(Str::base64($this->atom, $action, $urlSafe));
	}

	public function trim ($charlist = NULL, $mode = Str::BOTH) {
		$mode = $this->toAtom($mode);
		return $this->set(Str::trim($this->atom, $charlist, $mode));
	}

	public function reverse ($from = NULL, $length = NULL) {
		$from   = $this->toAtom($from);
		$length = $this->toAtom($length);
		return $this->set(Str::reverse($this->atom, $from, $length));
	}

	public function filter ($mode) {
		$mode = $this->toAtom($mode);
		return $this->set(Str::filter($this->atom, $mode));
	}

	public function fill ($length, $fillWith = ' ', $type = Str::RIGHT) {
		$length = $this->toAtom($length);
		$type = $this->toAtom($type);
		return $this->set(Str::fill($this->atom, $length, (string) $fillWith, $type));
	}

	public function format () {
		if (func_num_args() > 0) {
			$args = Map::create(func_get_args())
				->walk(array($this, 'toAtom'));
			$this->atom = func_num_args() > 1 ?
				Str::format($this->atom, $args->getAtom()) :
				Str::format($this->atom, $this->toAtom($args[0]));
		}
		return $this;
	}

	// Different
	public function cut ($from, $length = null) {
		$from   = $this->toAtom($from);
		$length = $this->toAtom($length);
		$func = $this->isMb() ? 'mb_substr' : 'substr';
		$substr = $length ? $func($this->atom, $from, $length) : $func($this->atom, $from);
		return $this->set($substr);
	}

	public function beginsWith ($str, $caseInsensitive = false) {
		$str = new String(strval($str));
		$comp = $this->copy()->cut(0, $str->length());
		return !$caseInsensitive ? $comp->equals($str) : 
			$comp->changeCase(Str::ALL, Str::DOWN)
				->equals($str->changeCase(Str::ALL, Str::DOWN));
	}

	public function endsWith ($str, $caseInsensitive = false) {
		$str = $this->toObject(strval($str))->pregQuote('/');
		$ci = $caseInsensitive ? 'i' : '';
    	return $this->pregTest("/$str$/$ci");
	}

	public function split ($by = 1, $limit = null) {
		$limit = $this->toAtom($limit);
		$by    = $this->toAtom($by);
		if (!(is_string($by)) and !(is_int($by) and $by > 0)) {
			// Of is not string and in not integer greater than 0, default:
			$by = 1;
		}
		
		if (is_int($by)) {
			$map = new Map(
				$this->isMb() ? $this->mbSplit($this->atom, $by) :
					str_split($this->atom, $by)
			);
			return is_int($limit) ? $map->limit($limit) : $map;
		} else {
			return is_int($limit) ? 
				new Map(explode($by, $this->atom, $limit)) :
				new Map(explode($by, $this->atom));
		}
	}

	public function count ($str = null) {
		if ($str) {
			$countFunc = $this->isMb() ? 'mb_substr_count' : 'substr_count';
			return new Number($countFunc($this->atom, strval($str)));
		} else {
			$allChars = $this->split();
			$chars = new Map;
			while ($allChars->each($value)) {
				isset($chars[$value]) ? $chars[$value]+= 1 : $chars[$value] = 1;
			}
			return new Map($chars);
		}
	}

	public function replace ($what, $with) {
		$what = $this->toAtom($what);
		if ($this->isClosure($with)) {
        	if (!is_array($what)) {
        		$what = array($what);
			}
			foreach ($what as $r) {
				$this->atom = str_replace($r, $with($this->toObject($r)), $this->atom);
			}
			return $this;
		} else {
			$with = $this->toAtom($with);
			return $this->set(str_replace($what, $with, $this->atom));
		}
	}

	public function in () {
		$args = new Map(func_get_args());
		$searchIn = ($args[0] instanceof Map)
			? $args[0] : $args;
		return $searchIn->hasValue($this);
	}

	public function repeat ($multiplier) {
		$multiplier = $this->toAtom($multiplier);
		return $this->set(str_repeat ($this->atom, $multiplier));
	}

	public function hash ($algo = 'md5', $rowOutput = false) {
		$this->atom =
			$rowOutput       ? hash  ($algo, $this->atom, true) : (
            $algo == 'md5'   ? md5   ($this->atom) : (
            $algo == 'sha1'  ? sha1  ($this->atom) : (
            $algo == 'crc32' ? crc32 ($this->atom) : (
            	hash ($algo, $this->atom)
        ))));
		return $this;
	}

	// PCRE
	public function pregQuote ($delimiter = NULL) {
		return $this->set(preg_quote($this->atom, $delimiter));
	}

	public function pregFilter ($pattern, $replacement, $limit = -1, &$count = null) {
		$pattern     = $this->toAtom($pattern);
		$replacement = $this->toAtom($replacement);
		$limit       = $this->toAtom($limit);
		$this->atom  = preg_filter ($pattern, $replacement, $this->atom, $limit, $count);
		$count       = $this->toObject($count);
		return $this;
	}

	public function pregSplit ($pattern, $limit = -1, $flags = 0) {
		$pattern = $this->toAtom($pattern);
		$count   = $this->toAtom($limit);
		$flags   = $this->toAtom($flags);
		return new Map(preg_split($pattern, $this->atom, $limit, $flags));
	}

	public function pregMatchAll ($pattern, &$count = null, $flags = 0, $offset = 0) {
		$pattern = $this->toAtom($pattern);
		$flags   = $this->toAtom($flags);
		$offset  = $this->toAtom($offset);
		$matches = array ();
		$count   = preg_match_all ($pattern, $this->atom, $matches, $flags, $offset);
		$count   = $this->toObject($count);
		return new Map($matches);
	}

	public function pregTest ($pattern, $flags = 0, $offset = 0) {
		$pattern = $this->toAtom($pattern);
		$flags   = $this->toAtom($flags);
		$offset  = $this->toAtom($offset);
		return (bool) preg_match ($pattern, $this->atom, $n, $flags, $offset);
	}

	public function pregMatch ($pattern, &$count = null, $flags = 0, $offset = 0) {
		$pattern = $this->toAtom($pattern);
		$flags   = $this->toAtom($flags);
		$offset  = $this->toAtom($offset);
		$count = preg_match ($pattern, $this->atom, $matches, $flags, $offset);
		$count = $this->toObject($count);
		return new Map($matches);
	}

	public function pregReplace ($pattern, $replacement, $limit = -1, &$count = null) {
		$pattern     = $this->toAtom($pattern);
		$limit       = $this->toAtom($limit);
		if ($this->isClosure($replacement)) {
			$func = 'preg_replace_callback';
		} else {
			$replacement = $this->toAtom($replacement);
			$func = 'preg_replace';
		}
		$this->atom  = $func($pattern, $replacement, $this->atom, $limit, $count);
		$count       = $this->toObject($count);
		return $this;
	}

	public function pregReplaceCallback ($pattern, $replacement, $limit = -1, &$count = null) {
		$limit = $this->toAtom($limit);
		$this->atom = preg_replace_callback($pattern, $replacement, $this->atom, $limit, $count);
		$count = $this->toObject($count);
		return $this;
	}

    // Serializable
	public function serialize() {
		return serialize($this->atom);
	}
    public function unserialize($data) {
        $this->atom = unserialize($data);
    }

	protected function mbSplit($str, $length = 1) {
		if ($length < 1) return FALSE;

		$result = array();
		for ($i = 0, $l = mb_strlen($str); $i < $l; $i += $length) {
			$result[] = mb_substr($str, $i, $length);
		}
		return $result;
	}
}

?>